package org.apache.maven.exception;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.building.ModelProblemUtils;
import org.apache.maven.plugin.AbstractMojoExecutionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginExecutionException;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.StringUtils;

/*

- test projects for each of these
- how to categorize the problems so that the id of the problem can be match to a page with descriptive help and the test
  project
- nice little sample projects that could be run in the core as well as integration tests

All Possible Errors
- invalid lifecycle phase (maybe same as bad CLI param, though you were talking about embedder too)
- <module> specified is not found
- malformed settings
- malformed POM
- local repository not writable
- remote repositories not available
- artifact metadata missing
- extension metadata missing
- extension artifact missing
- artifact metadata retrieval problem
- version range violation
- circular dependency
- artifact missing
- artifact retrieval exception
- md5 checksum doesn't match for local artifact, need to redownload this
- POM doesn't exist for a goal that requires one
- parent POM missing (in both the repository + relative path)
- component not found

Plugins:
- plugin metadata missing
- plugin metadata retrieval problem
- plugin artifact missing
- plugin artifact retrieval problem
- plugin dependency metadata missing
- plugin dependency metadata retrieval problem
- plugin configuration problem
- plugin execution failure due to something that is know to possibly go wrong (like compilation failure)
- plugin execution error due to something that is not expected to go wrong (the compiler executable missing)
- asking to use a plugin for which you do not have a version defined - tools to easily select versions
- goal not found in a plugin (probably could list the ones that are)

 */

//PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException, CycleDetectedInPluginGraphException;

@Component(role = ExceptionHandler.class)
public class DefaultExceptionHandler implements ExceptionHandler {

  public ExceptionSummary handleException(Throwable exception) {
    return handle("", exception);
  }

  private ExceptionSummary handle(String message, Throwable exception) {
    String reference = getReference(exception);

    List<ExceptionSummary> children = null;

    if (exception instanceof ProjectBuildingException) {
      List<ProjectBuildingResult> results = ((ProjectBuildingException) exception).getResults();

      children = new ArrayList<ExceptionSummary>();

      for (ProjectBuildingResult result : results) {
        ExceptionSummary child = handle(result);
        if (child != null) {
          children.add(child);
        }
      }

      message = "The build could not read " + children.size() + " project" + (children.size() == 1 ? "" : "s");
    } else {
      message = getMessage(message, exception);
    }

    return new ExceptionSummary(exception, message, reference, children);
  }

  private ExceptionSummary handle(ProjectBuildingResult result) {
    List<ExceptionSummary> children = new ArrayList<ExceptionSummary>();

    for (ModelProblem problem : result.getProblems()) {
      ExceptionSummary child = handle(problem, result.getProjectId());
      if (child != null) {
        children.add(child);
      }
    }

    if (children.isEmpty()) {
      return null;
    }

    String message = "\nThe project " + result.getProjectId() + " (" + result.getPomFile() + ") has " + children.size()
        + " error" + (children.size() == 1 ? "" : "s");

    return new ExceptionSummary(null, message, null, children);
  }

  private ExceptionSummary handle(ModelProblem problem, String projectId) {
    if (ModelProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
      String message = problem.getMessage();

      String location = ModelProblemUtils.formatLocation(problem, projectId);

      if (StringUtils.isNotEmpty(location)) {
        message += " @ " + location;
      }

      return handle(message, problem.getException());
    } else {
      return null;
    }
  }

  private String getReference(Throwable exception) {
    String reference = "";

    if (exception != null) {
      if (exception instanceof MojoExecutionException) {
        reference = MojoExecutionException.class.getSimpleName();

        Throwable cause = exception.getCause();
        if (cause instanceof IOException) {
          cause = cause.getCause();
          if (cause instanceof ConnectException) {
            reference = ConnectException.class.getSimpleName();
          }
        }
      } else if (exception instanceof MojoFailureException) {
        reference = MojoFailureException.class.getSimpleName();
      } else if (exception instanceof LinkageError) {
        reference = LinkageError.class.getSimpleName();
      } else if (exception instanceof PluginExecutionException) {
        Throwable cause = exception.getCause();

        if (cause instanceof PluginContainerException) {
          Throwable cause2 = cause.getCause();

          if (cause2 instanceof NoClassDefFoundError && cause2.getMessage().contains("org/sonatype/aether/")) {
            reference = "AetherClassNotFound";
          }
        }

        if (StringUtils.isEmpty(reference)) {
          reference = getReference(cause);
        }

        if (StringUtils.isEmpty(reference)) {
          reference = exception.getClass().getSimpleName();
        }
      } else if (exception instanceof LifecycleExecutionException) {
        reference = getReference(exception.getCause());
      } else if (isNoteworthyException(exception)) {
        reference = exception.getClass().getSimpleName();
      }
    }

    if (StringUtils.isNotEmpty(reference) && !reference.startsWith("http:")) {
      reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
    }

    return reference;
  }

  private boolean isNoteworthyException(Throwable exception) {
    if (exception == null) {
      return false;
    } else if (exception instanceof Error) {
      return true;
    } else if (exception instanceof RuntimeException) {
      return false;
    } else if (exception.getClass().getName().startsWith("java")) {
      return false;
    }
    return true;
  }

  private String getMessage(String message, Throwable exception) {
    String fullMessage = (message != null) ? message : "";

    for (Throwable t = exception; t != null; t = t.getCause()) {
      String exceptionMessage = t.getMessage();

      if (t instanceof AbstractMojoExecutionException) {
        String longMessage = ((AbstractMojoExecutionException) t).getLongMessage();
        if (StringUtils.isNotEmpty(longMessage)) {
          if (StringUtils.isEmpty(exceptionMessage) || longMessage.contains(exceptionMessage)) {
            exceptionMessage = longMessage;
          } else if (!exceptionMessage.contains(longMessage)) {
            exceptionMessage = join(exceptionMessage, '\n' + longMessage);
          }
        }
      }

      if (StringUtils.isEmpty(exceptionMessage)) {
        exceptionMessage = t.getClass().getSimpleName();
      }

      if (t instanceof UnknownHostException && !fullMessage.contains("host")) {
        fullMessage = join(fullMessage, "Unknown host " + exceptionMessage);
      } else if (!fullMessage.contains(exceptionMessage)) {
        fullMessage = join(fullMessage, exceptionMessage);
      }
    }

    return fullMessage.trim();
  }

  private String join(String message1, String message2) {
    String message = "";

    if (StringUtils.isNotEmpty(message1)) {
      message = message1.trim();
    }

    if (StringUtils.isNotEmpty(message2)) {
      if (StringUtils.isNotEmpty(message)) {
        if (message.endsWith(".") || message.endsWith("!") || message.endsWith(":")) {
          message += " ";
        } else {
          message += ": ";
        }
      }

      message += message2;
    }

    return message;
  }

}
